Removing the suggestion list
There's one final tweak we
will make to our auto-complete behavior. We should hide the suggestion
list when the user decides to do something else on the page. First of
all, we can react to the escape key in our keyup handler, and let the user dismiss the list that way:
else if (event.keyCode == 27 && selectedItem !== null) {
// User pressed escape key.
setSelectedItem(null);
}
More importantly, we should hide the list when the search field loses focus. A first attempt at this is quite simple:
$('#search-text').blur(function(event) {
setSelectedItem(null);
});
However, this causes
an unintended side effect. Since a mouse click on the list removes
focus from the field, this handler is called and the list is hidden.
That means that our click handler defined earlier never gets called, and it becomes impossible to interact with the list using the mouse.
There is no easy solution to this problem. The blur handler will always be called before the click handler. A workaround is to hide the list when the focus is lost, but to wait a fraction of a second first:
$('#search-text').blur(function(event) {
setTimeout(function() {
setSelectedItem(null);
}, 250);
});
This gives a chance for the click event to get triggered on the list item before the list item is hidden.
Auto-completion versus live search
The earlier example focused
on auto-completion of the text field, as it is a technique that applies
to many forms. However, for searches in particular, an alternative
called live search is preferred. This feature actually performs the content searches as the user types.
Functionally,
auto-completion and live search are very similar. In both cases, key
presses initiate an AJAX submission to the server, passing the current
field contents along with the request. The results are then placed in a
drop-down box below the field. In the case of auto-completion, as we
have seen, the results are possible search terms to use. With live
search, the results are the actual pages that contain the search terms
that have been typed.
On the JavaScript end, the code
to build these two features is nearly identical, so we won't go into
detail here. Deciding which to use is a matter of tradeoffs; live
search provides more information to the user with less effort, but is
typically more resource intensive.
The finished code
Our completed code for the search field's presentation and auto-complete behaviors is as follows:
$(document).ready(function() {
var $search = $('#search').addClass('overlabel');
var $searchInput = $search.find('input');
var $searchLabel = $search.find('label');
if ($searchInput.val()) {
$searchLabel.hide();
}
$searchInput
.focus(function() {
$searchLabel.hide();
})
.blur(function() {
if (this.value == '') {
$searchLabel.show();
}
});
$searchLabel.click(function() {
$searchInput.trigger('focus');
});
var $autocomplete = $('<ul class="autocomplete"></ul>')
.hide()
.insertAfter('#search-text');
var selectedItem = null;
var setSelectedItem = function(item) {
selectedItem = item;
if (selectedItem === null) {
$autocomplete.hide();
return;
}
if (selectedItem < 0) {
selectedItem = 0;
}
if (selectedItem >= $autocomplete.find('li').length) {
selectedItem = $autocomplete.find('li').length - 1;
}
$autocomplete.find('li').removeClass('selected')
.eq(selectedItem).addClass('selected');
$autocomplete.show();
};
var populateSearchField = function() {
$('#search-text').val($autocomplete
.find('li').eq(selectedItem).text());
setSelectedItem(null);
};
$('#search-text')
.attr('autocomplete', 'off')
.keyup(function(event) {
compact formsearch field codeif (event.keyCode > 40 || event.keyCode == 8) {
// Keys with codes 40 and below are special
// (enter, arrow keys, escape, etc.).
// Key code 8 is backspace.
$.ajax({
'url': '../search/autocomplete.php',
'data': {'search-text': $('#search-text').val()},
'dataType': 'json',
'type': 'GET',
'success': function(data) {
if (data.length) {
$autocomplete.empty();
$.each(data, function(index, term) {
$('<li></li>').text(term)
.appendTo($autocomplete)
.mouseover(function() {
setSelectedItem(index);
}).click(populateSearchField);
});
setSelectedItem(0);
}
else {
setSelectedItem(null);
}
}
});
}
else if (event.keyCode == 38 &&
selectedItem !== null) {
// User pressed up arrow.
setSelectedItem(selectedItem - 1);
event.preventDefault();
}
else if (event.keyCode == 40 &&
selectedItem !== null) {
// User pressed down arrow.
setSelectedItem(selectedItem + 1);
event.preventDefault();
}
else if (event.keyCode == 27 && selectedItem !== null) {
// User pressed escape key.
setSelectedItem(null);
compact formsearch field code}
}).keypress(function(event) {
if (event.keyCode == 13 && selectedItem !== null) {
// User pressed enter key.
populateSearchField();
event.preventDefault();
}
}).blur(function(event) {
setTimeout(function() {
setSelectedItem(null);
}, 250);
});
});